import { any } from "../core/logic";
import { Matrix4 } from "../core/math/Matrix4";
import { Component, ComponentType } from "./Component";
import { EventType } from "../event/index";
import { Buffer } from "../gpu/types/Buffer";
import { Vector3 } from "../core/math/Vector3";
import { Engine } from "../Engine";
import { Register } from "../register/Register";
import { Channel, Sampler, Animation } from "./animation/index";
class Camera extends Component {
    constructor(opts = {}, share) {
        opts.type = any(opts.type, ComponentType.Camera);
        super(opts, share);
        this._expand = any(opts._expand, 10);
        this._size = Engine.instance.size;
        this.on(EventType.Resize, this.resize);
        //记录相机锚点位置
        this._at = new Vector3(0, 0, 0);
        //投影
        this._aspect = this.size[0] / this.size[1];
        this._fovy = any(opts.fovy, Math.PI / 3);
        this._near = any(opts.near, 1);
        this._far = any(opts.far, 10000);
        this._autoNearFar = any(opts.autoNearFar, false);
        //视图
        this._view = new Matrix4();
        this._project = new Matrix4();
        this._vp = new Matrix4();
        this._vpInvert = new Matrix4();
        this._rotation = new Matrix4();
        this._roll = 0;
        this._pitch = 0;
        this.addParam("camera");
        this.autoUpdate = true;
        this.update();
    }

    set expand(v) {
        this._expand = v;
        return this;
    }
    get expand() {
        return this._expand;
    }
    get at() {
        return this._at;
    }
    set at(v) {
        if (v.length < 3) {
            v[2] = 0;
        }
        let dif = [v[0] - this.at.x, v[1] - this.at.y, v[2] - this.at.z];
        this.at.fromArray(v);
        this.trans.position = [this.trans.position.x + dif[0], this.trans.position.y + dif[1], this.trans.position.z + dif[2]];
        this.updateNearFar();
        return this;
    }

    attach(eid) {
        //绑定trans
        this._trans = Register.instance.entityManager.get(eid).getComponent(ComponentType.Trans);
        this._trans.onChange(this.onchange.bind(this));
        super.attach(eid);
    }
    onchange() {
        this.setState(false, "project");
        this.setState(false, "vp");
        this.addParam("camera");
        this.update();
    }

    get pitch() {
        return this._pitch;
    }
    set pitch(v) {
        this._pitch = v % (Math.PI * 2);
        let distance = this.trans.position.distanceTo(this.at);
        this.trans.position = [this.at.x, this.at.y, distance];
        this.trans.rotation = [0, 0, 0];
        this.trans.translateZ(-distance);
        this.trans.rotateZ(this.roll);
        this.trans.rotateX(this.pitch);
        this.trans.translateZ(distance);
        this.addParam("camera");
        return this;
    }
    set autoNearFar(v) {
        this._autoNearFar = v;
    }
    get autoNearFar() {
        return this._autoNearFar;
    }
    updateNearFar() {
        if (this._autoNearFar) {
            this._far = this.distance * this.expand;
            this._near = Math.max(this.far / 10000, 0.00001);
        }
    }
    get roll() {
        return this._roll;
    }
    set roll(v) {
        this._roll = v % (Math.PI * 2);
        let distance = this.trans.position.distanceTo(this.at);
        this.trans.position = [this.at.x, this.at.y, distance];
        this.trans.rotation = [0, 0, 0];
        this.trans.translateZ(-distance);
        this.trans.rotateZ(this.roll);
        this.trans.rotateX(this.pitch);
        this.trans.translateZ(distance);
        this.addParam("camera");
        return this;
    }
    set distance(v) {
        this.trans.position = [this.at.x, this.at.y, v];
        this.trans.rotation = [0, 0, 0];
        this.trans.translateZ(-v);
        this.trans.rotateZ(this.roll);
        this.trans.rotateX(this.pitch);
        this.trans.translateZ(v);
        this.addParam("camera");
        this.updateNearFar();
        return this;
    }
    get distance() {
        return this.trans.position.distanceTo(this.at);
    }
    get trans() {
        return this._trans;
    }
    get size() {
        return this._size;
    }
    updateState() {
        this.setState(true);
    }
    resize(event) {
        this._size = event.size;
        this.aspect = this._size[0] / this._size[1];
        this.addParam("camera");
        return this;
    }
    set aspect(v) {
        this._aspect = v;
        this.setState(false, "project");
        this.setState(false, "vp");
        this.addParam("camera");
        this.update();
        return this;
    }
    get aspect() {
        return this._aspect;
    }
    set fovy(v) {
        this._fovy = v;
        this.setState(false, "project");
        this.setState(false, "vp");
        this.addParam("camera");
        this.update();
        return this;
    }
    get fovy() {
        return this._fovy;
    }
    set near(v) {
        this._near = v;
        this.setState(false, "project");
        this.setState(false, "vp");
        this.addParam("camera");
        this.update();
        return this;
    }
    get near() {
        return this._near;
    }
    set far(v) {
        this._far = v;
        this.setState(false, "project");
        this.setState(false, "vp");
        this.addParam("camera");
        this.update();
        return this;
    }
    get far() {
        return this._far;
    }
    get view() {
        const trans = this.trans;
        if (!trans) {
            return null;
        }
        this._view = this.trans.matrixWorld.clone().invert();
        //let v = this.makeLookAtVector(this.trans.position, this.at, new Vector3(0, 1, 0));
        return this._view;
    }
    get resolution() {
        return this.distance * Math.tan(this.fovy / 2) / this.size[1];
    }
    //备份
    makeLookAtVector(eye, center, up) {
        var z = eye.clone().sub(center).normalize();
        var x = up.clone().cross(z).normalize();
        var y = z.clone().cross(x).normalize();

        var m = new Matrix4().set(x.x, x.y, x.z, 0,
            y.x, y.y, y.z, 0,
            z.x, z.y, z.z, 0,
            0, 0, 0, 1);

        var t = new Matrix4().set(1, 0, 0, -eye.x,
            0, 1, 0, -eye.y,
            0, 0, 1, -eye.z,
            0, 0, 0, 1);
        return m.multiply(t);
    }
    flyto(pose = {}, dual = 1, easing = "none", delay = 0, finish) {
        let animation = new Animation({ id: "flyto_" + this.id, loop: 1, keep: false, finalFrameCallback: finish });

        let at = any(pose.at, this.at.toArray());
        let distance = any(pose.distance, this.distance);
        let roll = any(pose.roll, this.roll);
        let pitch = any(pose.pitch, this.pitch);
        let sampleAt = new Sampler([delay, delay + dual], [this.at.toArray(), at], "LINEAR", easing);
        let channelAt = new Channel(this, "at");
        channelAt.add(sampleAt);
        animation.add(channelAt);

        let sampleDistance = new Sampler([delay, delay + dual], [this.distance, distance], "LINEAR", easing);
        let channelDistance = new Channel(this, "distance");
        channelDistance.add(sampleDistance);
        animation.add(channelDistance);

        let sampleRoll = new Sampler([delay, delay + dual], [this.roll, roll], "LINEAR", easing);
        let channelRoll = new Channel(this, "roll");
        channelRoll.add(sampleRoll);
        animation.add(channelRoll);

        let samplePitch = new Sampler([delay, delay + dual], [this.pitch, pitch], "LINEAR", easing);
        let channelPitch = new Channel(this, "pitch");
        channelPitch.add(samplePitch);
        animation.add(channelPitch);
        Engine.instance.register.manager(ComponentType.Animation).add(animation);
        animation.start();
    }
    get project() {
        if (!this.getState("project")) {
            let top = this.near * Math.tan(0.5 * this.fovy);
            let height = 2 * top;
            let width = this.aspect * height;
            let left = - 0.5 * width;
            this._project.makePerspective(left, left + width, top, top - height, this.near, this.far);
            this.setState(true, "project");
        }
        return this._project;
    }
    get vp() {
        if (!this.getState("vp")) {
            this._vp.multiplyMatrices(this.project, this.view);
            this._vpInvert = this._vp.clone().invert();
            this.setState(true, "vp");
        }
        return this._vp;
    }
    get vpInvert() {
        if (!this.getState("vp")) {
            this._vp.multiplyMatrices(this.project, this.view);
            this._vpInvert = this._vp.clone().invert();
            this.setState(true, "vp");
        }
        return this._vpInvert;
    }
    get rotation() {
        this._rotation.makeRotationFromQuaternion(this.trans.quaternion);
        // this._rotation.invert();
        return this._rotation;
    }
    // get distance() {
    //     return this.trans.distance;
    // }
    lookAt(at) {
        if (Array.isArray(at)) {
            if (at.length < 3) {
                at[2] = 0;
            }
            this._at.fromArray(at);
        } else {
            this._at = at;
        }
        this.trans.lookAt(at);
    }

    deactive() {
        super.deactive();
        this.addParam("camera");
    }
    active() {
        super.active();
        this.update();
    }
    pixelToWorld(pixel) {
        //ndc.x=(2*win.x-w)/w ndc.y=(2*win.y-h)/h;
        let ndc = [(2 * pixel[0] - this.size[0]) / this.size[0], (this.size[1] - 2 * pixel[1]) / this.size[1]];
        let out;
        if (pixel[2]) {
            out = this.unproj(ndc, pixel[2]);
        } else {
            out = this.unproj(ndc);
        }

        return out;
    }
    worldToPixel(position) {
        let ndc = this.proj(position);
        let px = (ndc.x * this.size[0] + this.size[0]) / 2;
        let py = (this.size[1] - ndc.y * this.size[1]) / 2;
        return [px, py];
    }
    /**
     * 像素坐标反投影到NDC坐标。
     * @param {Array(2)|vec2} position 【必选】像素坐标
     * @param {Number} zw 【可选】反投影的世界坐标系的z平面，缺省：0。
     * @return {vec3|Array(3)} 返回NDC坐标。
     */
    unproj(position = [0, 0], zw = 0) {
        const x = position[0];
        const y = position[1];
        let z = position[2];
        let vpInvert = this.vp.clone().invert();
        let m = vpInvert.elements;
        const fm = m[10] - zw * m[11];
        if (z == undefined || z == null) {
            z = (zw * m[3] * x + zw * m[7] * y + zw * m[15] - m[2] * x - m[6] * y - m[14]) / fm;
        }
        let out = new Vector3(x, y, z);
        out.applyMatrix4(vpInvert);
        return out;
    }
    /**
     * @param {vec3|Array(3)} position 【必选】 世界坐标系坐标。
     * @returns {vec3|Array(3)} NDC坐标系坐标。
     */
    proj(position) {
        let out = new Vector3().fromArray(position);
        out.applyMatrix4(this.vp);
        return out;
    }
    toGPU() {
        let cameraParam = this.getParam("camera");
        if (!this.farLog) { this.farLog = 2.0 / (Math.log(this.far + 1.0) / Math.LN2) }
        if (!cameraParam) {
            let data = new Float32Array([...this.vp.elements,
            ...this.vpInvert.elements,
            ...this.view.elements,
            ...this.project.elements,
            ...this.rotation.elements,
            ...this.trans.matrixWorld.elements,
            this.trans.position.x, this.trans.position.y, this.trans.position.z, this.distance,
            ...this.size, this.near, this.far, ...this.at]);
            cameraParam = new Buffer({
                id: "camera",
                name: "camera",
                usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
                visibility: GPUShaderStage.FRAGMENT | GPUShaderStage.VERTEX,
                type: "uniform",
                size: 432,
                data
            });
            this.addParam("camera", cameraParam);
            cameraParam.state = true;
            data = null;//销毁
        } else {
            if (!cameraParam.state) {
                let data = new Float32Array([...this.vp.elements,
                ...this.vpInvert.elements,
                ...this.view.elements,
                ...this.project.elements,
                ...this.rotation.elements,
                ...this.trans.matrixWorld.elements, this.trans.position.x, this.trans.position.y, this.trans.position.z, this.distance,
                ...this.size, this.near, this.far, ...this.at]);
                cameraParam.buffer(data);
                cameraParam.state = true;
                data = null;//销毁
            }
        }
        // console.log(this.distance);
    }
}
export { Camera }